/**
 *    Copyright 2009-2015 the original author or authors.
 *
 *    Licensed under the Apache License, Version 2.0 (the "License");
 *    you may not use this file except in compliance with the License.
 *    You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 *    Unless required by applicable law or agreed to in writing, software
 *    distributed under the License is distributed on an "AS IS" BASIS,
 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *    See the License for the specific language governing permissions and
 *    limitations under the License.
 */
package org.apache.ibatis.executor.keygen;

import java.sql.Statement;
import java.util.List;

import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.executor.ExecutorException;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.ExecutorType;
import org.apache.ibatis.session.RowBounds;

/**
 * @author Clinton Begin
 * @author Jeff Butler
 * 
 *         通过自定义sql手动获取主键值，有2种配置，before和after,
 *         before既是在insert之前设置到pojo中作为参数一起insert到db,after即为在insert之后，
 *         通过自定义sql获取并设置到pojo中 如：
 *         <selectKey resultType="java.lang.Integer" keyProperty="id" order=
 *         "BEFORE"> select max(id) from TB_USER </selectKey>
 */
public class SelectKeyGenerator implements KeyGenerator {
	/**
	 * 前缀
	 */
	public static final String SELECT_KEY_SUFFIX = "!selectKey";
	/**
	 * 是否为执行前
	 */
	private boolean executeBefore;
	/**
	 * 映射的语句
	 */
	private MappedStatement keyStatement;

	public SelectKeyGenerator(MappedStatement keyStatement, boolean executeBefore) {
		this.executeBefore = executeBefore;
		this.keyStatement = keyStatement;
	}

	@Override
	public void processBefore(Executor executor, MappedStatement ms, Statement stmt, Object parameter) {
		if (executeBefore) {
			processGeneratedKeys(executor, ms, parameter);
		}
	}

	@Override
	public void processAfter(Executor executor, MappedStatement ms, Statement stmt, Object parameter) {
		if (!executeBefore) {
			processGeneratedKeys(executor, ms, parameter);
		}
	}
	/**
	 * 生成键值对的过程
	 * @param executor
	 * @param ms
	 * @param parameter
	 */
	// 执行来获得结果值
	private void processGeneratedKeys(Executor executor, MappedStatement ms, Object parameter) {
		try {
			if (parameter != null && keyStatement != null && keyStatement.getKeyProperties() != null) {
				String[] keyProperties = keyStatement.getKeyProperties();
				final Configuration configuration = ms.getConfiguration();
				final MetaObject metaParam = configuration.newMetaObject(parameter);
				if (keyProperties != null) {
					// Do not close keyExecutor.
					// The transaction will be closed by parent executor.
					Executor keyExecutor = configuration.newExecutor(executor.getTransaction(), ExecutorType.SIMPLE);
					// 执行获得主键的sql语句
					List<Object> values = keyExecutor.query(keyStatement, parameter, RowBounds.DEFAULT, Executor.NO_RESULT_HANDLER);
					if (values.size() == 0) {
						throw new ExecutorException("SelectKey returned no data.");
					} else if (values.size() > 1) {
						throw new ExecutorException("SelectKey returned more than one value.");
					} else {
						// 执行结果
						MetaObject metaResult = configuration.newMetaObject(values.get(0));
						// 返回参数如果是一个
						if (keyProperties.length == 1) {
							// 判断返回参数使用有get方法
							if (metaResult.hasGetter(keyProperties[0])) {
								setValue(metaParam, keyProperties[0], metaResult.getValue(keyProperties[0]));
							} else {
								// no getter for the property - maybe just a
								// single value object
								// so try that
								setValue(metaParam, keyProperties[0], values.get(0));
							}
						} else {
							// 如果有多个返回参数则需要循环赋值
							handleMultipleProperties(keyProperties, metaParam, metaResult);
						}
					}
				}
			}
		} catch (ExecutorException e) {
			throw e;
		} catch (Exception e) {
			throw new ExecutorException("Error selecting key or setting result to parameter object. Cause: " + e, e);
		}
	}
	/**
	 * 多参处理器
	 * @param keyProperties
	 * @param metaParam
	 * @param metaResult
	 */
	// 如果有多个返回参数则需要循环赋值
	private void handleMultipleProperties(String[] keyProperties, MetaObject metaParam, MetaObject metaResult) {
		String[] keyColumns = keyStatement.getKeyColumns();
		// 如果没设置返回参数的值，则使用结果属性的值
		if (keyColumns == null || keyColumns.length == 0) {
			// no key columns specified, just use the property names
			for (int i = 0; i < keyProperties.length; i++) {
				setValue(metaParam, keyProperties[i], metaResult.getValue(keyProperties[i]));
			}
		} else {
			if (keyColumns.length != keyProperties.length) {
				throw new ExecutorException(
						"If SelectKey has key columns, the number must match the number of key properties.");
			}
			for (int i = 0; i < keyProperties.length; i++) {
				setValue(metaParam, keyProperties[i], metaResult.getValue(keyColumns[i]));
			}
		}
	}

	/**
	 *  设置返回参数对应的值
	 * @param metaParam
	 * @param property
	 * @param value
	 */
	private void setValue(MetaObject metaParam, String property, Object value) {
		if (metaParam.hasSetter(property)) {
			metaParam.setValue(property, value);
		} else {
			throw new ExecutorException("No setter found for the keyProperty '" + property + "' in "
					+ metaParam.getOriginalObject().getClass().getName() + ".");
		}
	}
}
